#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <mysql.h>
#include <unistd.h>
#include <sys/syscall.h>

#include "dbm.h"

#define DBM_LOG(level,fmt,args...) do{printf("[%s][%s:%d]",#level,__FUNCTION__,__LINE__);printf(fmt,##args);}while(0)
#define MYSQL_RELEASE_RESULT(result) \
    do{MYSQL_RES *result=mysql_store_result(myconn);mysql_free_result(result);}while(!mysql_next_result(myconn))

struct dbm_mysql_conn
{
    MYSQL *myconn;
    pthread_t tid;
    pid_t wid;
    pid_t pid;
};
struct dbm_mysql_conns_pool
{
    pthread_mutex_t lock;
    int max_conns;
    int left_conns;
    struct dbm_mysql_conn *conns;
};

static struct dbm_mysql_conns_pool mypool;

int print_sql_result(DB_EXEC_RES myres,void *user_data, int size)
{
    int i = 0;
    MYSQL_RES *result = (MYSQL_RES*)myres;
    MYSQL_ROW row;

    int ncol = mysql_num_fields(result);
    while ((row = mysql_fetch_row(result)))
    {// null indicates there no more row.
	unsigned long *lengths;
	lengths = mysql_fetch_lengths(result);
	for(i = 0; i < ncol; i++)
	{
	    printf("[%.*s] ", (int) lengths[i], row[i] ? row[i] : "NULL");
	}
	printf("\n");
    }
    return 0;
}

int mysql_cmd_handle3(DB_CONN_HDL dbconn,const char *sql,HANDLE_MSQL_CB func,void *user_data,int size)
{
    int col_num = 0,row_num = 0;
    int res_num = 0,ret = 0;
    MYSQL *myconn = (MYSQL*)dbconn;
    MYSQL_RES *result;

    if(myconn==NULL || sql==NULL)
	return -1;

    int try_cnt = 100;
    ret = mysql_query(myconn,sql);
    while(ret && mysql_errno(myconn)==2013 && try_cnt--)
    {/* The connection to the server was lost during the query. */
	usleep(50);
	ret = mysql_query(myconn,sql);
    }
    if(ret)
    {
	DBM_LOG(ERR,"mysql exec[%s] fail:%s\n",sql,mysql_error(myconn));
	return -1;
    }
    do
    {
	res_num++;
	result = mysql_store_result(myconn);
	if(result)
	{
	    col_num = mysql_num_fields(result);
	    row_num = mysql_num_rows(result);
	    //printf("---fields num = %d,row num = %d\n",col_num,row_num);
	    if(func)
	    {
		ret = func(result,res_num,user_data,size);
	    }
	    mysql_free_result(result);
	}
	else
	{
	    if(mysql_field_count(myconn)==0)
	    {
		// query does not return data
		// (it was not a SELECT)
		row_num = mysql_affected_rows(myconn);
		//printf("---affected rows = %d\n",row_num);
	    }
	    else
	    {
		// mysql_store_result() should have returned data
		DBM_LOG(ERR, "mysql exec[%s]  error: %s\n", sql,mysql_error(myconn));
		ret = -1;
	    }
	}
    }while(!mysql_next_result(myconn)); /* 0 - more results */
    //printf("----result set number = %d\n",res_num);
    return ret;
}

int mysql_cmd_handle2(DB_CONN_HDL dbconn,const char *sql,HANDLE_SQL_CB func,void *user_data,int size)
{
    int col_num = 0,row_num = 0;
    int res_num = 0,ret = 0;
    MYSQL *myconn = (MYSQL*)dbconn;
    MYSQL_RES *result;

    if(myconn==NULL || sql==NULL)
	return -1;

    int try_cnt = 100;
    ret = mysql_query(myconn,sql);
    while(ret && mysql_errno(myconn)==2013 && try_cnt--)
    {/* The connection to the server was lost during the query. */
	usleep(50);
	ret = mysql_query(myconn,sql);
    }
    if(ret)
    {
	DBM_LOG(ERR,"mysql exec[%s] fail:%s\n",sql,mysql_error(myconn));
	return -1;
    }
    do
    {
	res_num++;
	result = mysql_store_result(myconn);
	if(result)
	{
	    col_num = mysql_num_fields(result);
	    row_num = mysql_num_rows(result);
	    //printf("---fields num = %d,row num = %d\n",col_num,row_num);
	    if(func)
	    {
		ret = func(result,user_data,size);
	    }
	    mysql_free_result(result);
	}
	else
	{
	    if(mysql_field_count(myconn)==0)
	    {
		// query does not return data
		// (it was not a SELECT)
		row_num = mysql_affected_rows(myconn);
		//printf("---affected rows = %d\n",row_num);
	    }
	    else
	    {
		// mysql_store_result() should have returned data
		DBM_LOG(ERR, "mysql exec[%s]  error: %s\n", sql,mysql_error(myconn));
		ret = -1;
	    }
	}
    }while(!mysql_next_result(myconn)); /* 0 - more results */
    //printf("----result set number = %d\n",res_num);
    return ret;
}

int mysql_cmd_handle(DB_CONN_HDL dbconn,const char *sql,HANDLE_SQL_CB func)
{
    return mysql_cmd_handle2(dbconn,sql,func,NULL,0);
}

int dbm_init(unsigned int max_conns)
{
    if(mysql_library_init(0,NULL,NULL))
    {
	DBM_LOG(ERR,"mysql_library_init fail!\n");
	return -1;
    }
    pthread_mutex_init(&mypool.lock,NULL);
    mypool.conns = (struct dbm_mysql_conn*)calloc(max_conns,sizeof(struct dbm_mysql_conn));
    if(mypool.conns==NULL)
    {
	DBM_LOG(ERR,"calloc mysql conns pool[%d] fail!\n",mypool.max_conns);
	return -1;
    }
    mypool.max_conns = max_conns;
    mypool.left_conns = mypool.max_conns;
    return 0;  
}

int dbm_exit(void)
{
    int i = 0;
    pthread_mutex_lock(&mypool.lock);
    for(i=0;i<mypool.max_conns;i++)
    {
	if(mypool.conns[i].myconn!=NULL)
	{
	    mysql_close(mypool.conns[i].myconn);
	    mypool.conns[i].myconn = NULL;
	}
    }
    pthread_mutex_unlock(&mypool.lock);
    pthread_mutex_destroy(&mypool.lock);
    if(mypool.conns!=NULL)
    {
	free(mypool.conns);
	mypool.conns = NULL;
    }
    mysql_library_end();
    return 0;
}

DB_CONN_HDL dbm_find_myconn(void)
{
    pthread_t tid;
    pid_t wid,pid;

    tid = pthread_self();
    wid = syscall(SYS_gettid);
    pid = getpid();

    int i = 0;
    MYSQL *myconn = NULL;
    pthread_mutex_lock(&mypool.lock);
    for(i=0;i<mypool.max_conns;i++)
    {
	if(tid==mypool.conns[i].tid && wid==mypool.conns[i].wid && pid==mypool.conns[i].pid)
	{
	    myconn = mypool.conns[i].myconn;
	    break;
	}
    }
    pthread_mutex_unlock(&mypool.lock);
    return (DB_CONN_HDL)myconn;
}

int dbm_add_myconn(MYSQL *myconn)
{
    pthread_t tid;
    pid_t wid,pid;

    tid = pthread_self();
    wid = syscall(SYS_gettid);
    pid = getpid();

    int i = 0,ret = -1;
    pthread_mutex_lock(&mypool.lock);
    for(i=0;i<mypool.max_conns;i++)
    {
	if(mypool.conns[i].myconn==NULL)
	{
	    mypool.conns[i].tid = tid;
	    mypool.conns[i].wid = wid;
	    mypool.conns[i].pid = pid;
	    mypool.conns[i].myconn = myconn;
	    
	    mypool.left_conns--;
	    ret = 0;
	    break;
	}
    }
    pthread_mutex_unlock(&mypool.lock);
    if(ret < 0 && myconn!=NULL)
    {// we should close conn when add fail
	mysql_close(myconn);
	mysql_thread_end();
    }
    return ret;
}

int dbm_del_myconn(void)
{
    pthread_t tid;
    pid_t wid,pid;

    tid = pthread_self();
    wid = syscall(SYS_gettid);
    pid = getpid();

    int i = 0,ret = -1;
    pthread_mutex_lock(&mypool.lock);
    for(i=0;i<mypool.max_conns;i++)
    {
	if(tid==mypool.conns[i].tid && wid==mypool.conns[i].wid && pid==mypool.conns[i].pid)
	{
	    mysql_close(mypool.conns[i].myconn);
	    mysql_thread_end();
	    mypool.conns[i].myconn = NULL;
	    memset(&mypool.conns[i],0,sizeof(mypool.conns[i]));

	    mypool.left_conns++;
	    ret = 0;
	    break;
	}
    }
    pthread_mutex_unlock(&mypool.lock);
    return ret;
}

int dbm_connect2(const char *host,unsigned short port,const char *db,const char *username,const char *pwd)
{
    if(host==NULL || db==NULL || username==NULL || pwd==NULL)
	return -1;

    if(dbm_find_myconn()!=NULL)
    {
	DBM_LOG(WRN,"the routine has connect mysql!\n");
	return 0;
    }

    MYSQL *myconn = mysql_init(NULL);
    if(myconn==NULL)
    {
	DBM_LOG(ERR,"mysql_init fail!\n");
	return -1;
    }
    int reconnect = 1;
    char *charset = "utf8";
    mysql_options(myconn,MYSQL_OPT_RECONNECT,&reconnect);
    mysql_options(myconn,MYSQL_SET_CHARSET_NAME,charset);

    //if(mysql_real_connect(myconn,"localhost","dubhe","2014ipc","ipc",0,NULL,0)==NULL)
    if(mysql_real_connect(myconn,host,username,pwd,db,port,NULL,0)==NULL)
    {/* we use mysql-proxy to handle load balance and read-write seperate */
	DBM_LOG(ERR,"mysql_real_connect fail:%s\n",mysql_error(myconn));
	mysql_thread_end();
	return -1;
    }

    return dbm_add_myconn(myconn);
}

int dbm_connect(void)
{/* 4040 - mysql-proxy port */
    return dbm_connect2("119.147.144.89",4040,"ipc","dubhe","2014ipc");
}

int dbm_disconnect(void)
{
    return dbm_del_myconn();
}

int dbm_print_table(const char *tblname)
{
    if(tblname==NULL)
	return -1;

    char sqlcmd[SQL_CMD_MAX_LEN]={0};
    snprintf(sqlcmd,SQL_CMD_MAX_LEN,"select * from %s", tblname);
    
    return dbm_cmd_handle(sqlcmd,print_sql_result);
}

int dbm_cmd_handle(const char *sql,HANDLE_SQL_CB func)
{
    DB_CONN_HDL myconn = dbm_find_myconn();
    if(myconn==NULL||sql==NULL)
	return -1;
    return mysql_cmd_handle(myconn,sql,func);
}

int dbm_cmd_handle2(const char *sql,HANDLE_SQL_CB func,void *user_data,int size)
{
    DB_CONN_HDL myconn = dbm_find_myconn();
    if(myconn==NULL||sql==NULL)
	return -1;
    return mysql_cmd_handle2(myconn,sql,func,user_data,size);
}

int dbm_cmd_handle3(const char *sql,HANDLE_MSQL_CB func,void *user_data,int size)
{
    DB_CONN_HDL myconn = dbm_find_myconn();
    if(myconn==NULL||sql==NULL)
	return -1;
    return mysql_cmd_handle3(myconn,sql,func,user_data,size);
}

